﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Design;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using Arya.Data;
using Arya.Framework.Common;
using Arya.Framework.Common.ComponentModel;
using Arya.Framework.Common.Extensions;
using Arya.Framework.Extensions;
using Arya.Framework.GUI.TypeEditors;
using Arya.Framework4.Collections;
using log4net;
using ExtendedTaxonomyInfo = Arya.Framework4.ComponentModel.ExtendedTaxonomyInfo;
using TaxonomyCollectionConverter = Arya.Framework4.ComponentModel.TaxonomyCollectionConverter;
using TaxonomyEditor = Arya.Framework4.UI.TypeEditors.TaxonomyEditor;

namespace Arya.Framework4.IO.Exports
{
    [TypeConverter(typeof (PropertySorter))]
    [DefaultProperty("Taxonomies")]
    [Serializable]
    public abstract class ExportWorker : WorkerBase, ISerializable, IComponent
    {
        #region SortOrder enum

        public enum SortOrder
        {
            [DisplayTextAndValue("Order by Attribute Names only", null)] OrderbyAttributeNameOnly,
            [DisplayTextAndValue("Order by Disp.-Nav.-Attributes", null)] OrderbyDisplayNavigation,
            [DisplayTextAndValue("Order by Nav.-Disp.-Attributes", null)] OrderbyNavigationDisplay
        }

        #endregion

        #region SourceType enum

        public enum SourceType
        {
            [DisplayTextAndValue("Taxonomy", "Taxonomy")] Taxonomy,
            [DisplayTextAndValue("Sku List", "SkuList")] SkuList
        }

        #endregion

        public EventHandler PropertyChanged;

        public bool WorkerSupportsSaveOptions = true;
        private SourceType _exportSourceType;
        private bool allowMultipleTaxonomySelection;
        private string autoGeneratedFileName;
        private string exportFileName;
        private string[] skuCollection;

        private TaxonomyCollection taxonomyCollection = new TaxonomyCollection();

        protected ExportWorker(string argumentDirectoryPath, PropertyGrid ownerPropertyGrid) : base(argumentDirectoryPath)
        {
            CurrentLogWriter = LogManager.GetLogger(this.GetType());
            AllowMultipleTaxonomySelection = false;
            State = WorkerState.Working;

            //Hookup Refresh event with Collection Changes
            if (ownerPropertyGrid != null)
            {
                Taxonomies.ItemChanged += (sender, args) =>
                                          {
                                              ownerPropertyGrid.Refresh();
                                              autoGeneratedFileName = MakeValidFileName();
                                          };

                PropertyChanged += (sender, args) => ownerPropertyGrid.Refresh();
            }
        }

        protected ExportWorker(string argumentDirectoryPath, SerializationInfo info, StreamingContext ctxt)
            : base(argumentDirectoryPath)
        {
            CurrentLogWriter = LogManager.GetLogger(this.GetType());
            AllowMultipleTaxonomySelection = (bool) info.GetValue("AllowMultipleTaxonomySelection", typeof (bool));
            FieldDelimiter = (Delimiter) info.GetValue("FieldDelimiter", typeof (Delimiter));
        }

        [DisplayName(@"Multi-Node Selection Allowed"),
         Description("Indicates if user can select multiple nodes for export"), Category(CaptionRequired),
         PropertyOrder(RequiredBaseOrder + 1)]
        [ReadOnly(true)]
        [TypeConverter(typeof (BooleanToYesNoConverter))]
        public bool AllowMultipleTaxonomySelection
        {
            get { return allowMultipleTaxonomySelection; }
            set
            {
                allowMultipleTaxonomySelection = value;
                Taxonomies.MultipleNodes = value;
            }
        }

        [Description("Browse Export file .."), DisplayName(@"Export File Name"), Category(CaptionRequired),
         PropertyOrder(RequiredBaseOrder + 2)]
        [Editor(typeof (SaveFileNameEditor), typeof (UITypeEditor))]
        public string ExportFileName
        {
            get { return exportFileName; }
            set
            {
                if (Path.IsPathRooted(value))
                {
                    exportFileName = Path.Combine(
                        Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), value);
                }
                else
                {
                    var dirPath = Path.GetDirectoryName(value);
                    if (dirPath == null || !Directory.Exists(dirPath))
                    {
                        MessageBoxEx.Show("Invalid File Path" + Environment.NewLine + value, "Invalid File Path",
                            MessageBoxButtons.OK, MessageBoxIcon.Stop);
                    }
                }
                exportFileName = value;
            }
        }

        [Description("Field Delimiter to be used in the exported file"), DisplayName(@"Field Delimiter"),
         Category(CaptionRequired), DefaultValue(Delimiter.Tab), PropertyOrder(RequiredBaseOrder + 3)]
        [TypeConverter(typeof (CustomEnumConverter))]
        public Delimiter FieldDelimiter { get; set; }

        [Description("Export SKUs based on a Taxonomy Selection, or based on a SKU List"), DisplayName(@"Source Type"),
         Category(CaptionRequired)]
        [DefaultValue(SourceType.Taxonomy), PropertyOrder(RequiredBaseOrder + 4)]
        [TypeConverter(typeof (CustomEnumConverter))]
        [Browsable(false)]
        public SourceType ExportSourceType
        {
            get { return _exportSourceType; }
            set
            {
                _exportSourceType = value;

                SetBrowsableProperty(GetType(), "Taxonomies", _exportSourceType == SourceType.Taxonomy);
                SetBrowsableProperty(GetType(), "SkuCollection", _exportSourceType == SourceType.SkuList);

                if (PropertyChanged != null)
                    PropertyChanged(this, new EventArgs());
            }
        }

        [TypeConverter(typeof (TaxonomyCollectionConverter)), Category(CaptionRequired),
         PropertyOrder(RequiredBaseOrder + 5)]
        [Editor(typeof (TaxonomyEditor), typeof (UITypeEditor))]
        [Description("Taxonomies to be exported.")]
        [Browsable(true)]
        public TaxonomyCollection Taxonomies
        {
            get { return taxonomyCollection; }
            set
            {
                taxonomyCollection = value;
                autoGeneratedFileName = MakeValidFileName();
                ExportSourceType = SourceType.Taxonomy;
            }
        }

        [TypeConverter(typeof (StringArrayConverter)), Category(CaptionRequired), PropertyOrder(RequiredBaseOrder + 6)]
        //TODO: Ask Vivek for resolve 
        //[TypeConverter(typeof (StringCollectionConverter)), Category(CaptionRequired), PropertyOrder(RequiredBaseOrder + 6)]
        [Description("Skus to be exported")]
        [Browsable(false)]
        public string[] SkuCollection
        {
            get { return skuCollection; }

            set
            {
                skuCollection = value;
                ExportSourceType = SourceType.SkuList;
            }
        }

        #region IComponent Members

        public void Dispose()
        {
            // never called in this specific context with the PropertyGrid
            // but just reference the required Disposed event to avoid warnings
            if (Disposed != null)
                Disposed(this, EventArgs.Empty);
        }

        [Browsable(false)]
        public ISite Site
        {
            get { return new ExportDesignerVerbSite(this); }
            set { throw new NotImplementedException(); }
        }

        public event EventHandler Disposed;

        #endregion

        #region ISerializable Members

        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("AllowMultipleTaxonomySelection", AllowMultipleTaxonomySelection);
            info.AddValue("FieldDelimiter", FieldDelimiter);
        }

        #endregion

        public void SetOwnerGrid(PropertyGrid ownerPropertyGrid)
        {
            Taxonomies.ItemChanged += (sender, args) => ownerPropertyGrid.Refresh();
        }

        private string MakeValidFileName()
        {
            if (taxonomyCollection.Count == 0)
                return null;

            var fileName =
                taxonomyCollection.Cast<ExtendedTaxonomyInfo>()
                    .Select(p => p.Taxonomy.NodeName)
                    .Aggregate((a, b) => a + ", " + b);

            if (string.IsNullOrWhiteSpace(fileName))
                return null;

            var invalidChars = Regex.Escape(new string(Path.GetInvalidFileNameChars()));
            var invalidReStr = string.Format(@"[{0}]+", invalidChars);
            return Regex.Replace(fileName, invalidReStr, "_");
        }

        public virtual List<string> ValidateInput()
        {
            var errors = new List<string>(2);

            if (string.IsNullOrWhiteSpace(ExportFileName))
                errors.Add("Export file name not selected.");

            if (skuCollection == null && (taxonomyCollection == null || taxonomyCollection.Count == 0))
                errors.Add("Taxonomies must be selected for export.");

            return errors;
        }

        protected static decimal GetRank(SchemaData schemaData, SortOrder orderAttributesBy)
        {
            if (schemaData == null)
                return Int32.MaxValue;

            if (orderAttributesBy == SortOrder.OrderbyNavigationDisplay)
            {
                return schemaData.NavigationOrder > 0
                    ? schemaData.NavigationOrder
                    : schemaData.DisplayOrder > 0 ? schemaData.DisplayOrder + 1000 : Int32.MaxValue;
            }

            if (orderAttributesBy == SortOrder.OrderbyDisplayNavigation)
            {
                return schemaData.DisplayOrder > 0
                    ? schemaData.DisplayOrder
                    : schemaData.NavigationOrder > 0 ? schemaData.NavigationOrder + 1000 : Int32.MaxValue;
            }

            return 0;
        }

        protected static IEnumerable<Sku> GetFilteredSkuList(IEnumerable<Sku> skus, bool productTypeOnly,
            Dictionary<string, List<string>> skuInclusions, Dictionary<string, List<string>> skuExclusions)
        {
            if (skuInclusions != null)
            {
                foreach (var filter in skuInclusions)
                {
                    var attribute = filter.Key;

                    skus = filter.Value.Count == 0
                        ? skus.Where(sku => sku.HasAttribute(attribute))
                        : filter.Value.Aggregate(skus,
                            (current, value) => current.Where(sku => sku.HasValue(attribute, value)));
                }
            }

            if (skuExclusions != null)
            {
                foreach (var exclusion in skuExclusions)
                {
                    var attribute = exclusion.Key;

                    skus = exclusion.Value.Count == 0
                        ? skus.Where(sku => !sku.HasAttribute(attribute))
                        : exclusion.Value.Aggregate(skus,
                            (current, value) => current.Where(sku => !sku.HasValue(attribute, value)));
                }
            }

            if (productTypeOnly)
                skus = skus.Where(sku => sku.SkuType == Framework.Data.AryaDb.Sku.ItemType.Product.ToString());

            return skus;
        }

        protected void SetReadOnlyProperty(Type sourceType, string fieldName, bool isReadOnly)
        {
            var descriptor = TypeDescriptor.GetProperties(sourceType)[fieldName];
            var attribute = (ReadOnlyAttribute) descriptor.Attributes[typeof (ReadOnlyAttribute)];
            var fieldToChange = attribute.GetType()
                .GetField("isReadOnly", BindingFlags.NonPublic | BindingFlags.Instance);
            if (fieldToChange != null)
                fieldToChange.SetValue(attribute, isReadOnly);
        }

        protected void SetBrowsableProperty(Type sourceType, string fieldName, bool isBrowsable)
        {
            var descriptor = TypeDescriptor.GetProperties(sourceType)[fieldName];
            var attribute = (BrowsableAttribute) descriptor.Attributes[typeof (BrowsableAttribute)];

            var fieldToChange = attribute.GetType()
                .GetField("browsable", BindingFlags.NonPublic | BindingFlags.Instance);
            if (fieldToChange != null)
                fieldToChange.SetValue(attribute, isBrowsable);
        }

        public static Dictionary<string, List<string>> ParseSkuInclusionAndExclusions(IEnumerable<string> attributeList)
        {
            var filterExclusions = new Dictionary<string, List<string>>();

            if (attributeList == null)
                return filterExclusions;

            attributeList = attributeList.Where(p => !string.IsNullOrWhiteSpace(p));

            foreach (var attVal in attributeList)
            {
                var attributeValue = attVal.Split(new[] {"="}, StringSplitOptions.RemoveEmptyEntries);
                if (!attributeValue.Any())
                    continue;

                var lowerAttributeName = attributeValue[0].ToLower().Trim();
                if (!filterExclusions.ContainsKey(lowerAttributeName))
                    filterExclusions.Add(lowerAttributeName, new List<string>());

                if (attributeValue.Count() == 1)
                    continue;

                var attValues = filterExclusions[lowerAttributeName];
                attValues.Add(attributeValue[1].Trim());
            }
            return filterExclusions;
        }

        public static Dictionary<string, string> ParseSkuValueInclusions(IEnumerable<string> sourceValueFilters)
        {
            var parsedvalueFilters = new Dictionary<string, string>();

            if (sourceValueFilters == null)
                return parsedvalueFilters;

            sourceValueFilters = sourceValueFilters.Where(p => !string.IsNullOrWhiteSpace(p));

            foreach (var attVal in sourceValueFilters)
            {
                var fieldValue = attVal.Split(new[] {"="}, StringSplitOptions.RemoveEmptyEntries);
                if (!fieldValue.Any())
                    continue;

                var lowerFieldName = fieldValue[0].ToLower().Trim();
                var lowerFieldValue = fieldValue[1].ToLower().Trim();
                if (!parsedvalueFilters.ContainsKey(lowerFieldName))
                    parsedvalueFilters.Add(lowerFieldName, lowerFieldValue);
            }
            return parsedvalueFilters;
        }

        //protected static void SetDefaultValueProperty(Type sourceType,string fieldName, string value)
        //{
        //    var descriptor = TypeDescriptor.GetProperties(sourceType)[fieldName];
        //    var attribute = (DefaultValueAttribute)descriptor.Attributes[typeof(DefaultValueAttribute)];
        //    var fieldToChange = attribute.GetType().GetField("value",
        //                                     BindingFlags.NonPublic |
        //                                     BindingFlags.Instance);
        //    if (fieldToChange != null)
        //        fieldToChange.SetValue(attribute, value);
        //}

        //public static T GetDefaultValue<T>(Type sourceType,string propertyName)
        //{
        //    var property = sourceType.GetProperty(propertyName);

        //    var attribute = property
        //        .GetCustomAttribute(typeof(DefaultValueAttribute))
        //            as DefaultValueAttribute;

        //    if (attribute != null)
        //    {
        //        return (T)attribute.Value;
        //    }
        //}

        public void ResetWorkerStatus()
        {
            StatusMessage = "Ready";
            CurrentProgress = 0;
            MaximumProgress = 0;
            State = WorkerState.Ready;
        }

        [Browsable(true)]
        public void ClearTaxonomy()
        {
            taxonomyCollection.Clear();
        }
    }
}